<?php

namespace Dongasai\WkProtoPhp;

use Dongasai\WkProtoPhp\Enums\FrameType;
use Dongasai\WkProtoPhp\Packets\ConnectPacket;
use Dongasai\WkProtoPhp\Packets\ConnackPacket;
use Dongasai\WkProtoPhp\Packets\DisconnectPacket;
use Dongasai\WkProtoPhp\Packets\SendPacket;
use Dongasai\WkProtoPhp\Packets\SendackPacket;
use Dongasai\WkProtoPhp\Packets\RecvPacket;
use Dongasai\WkProtoPhp\Packets\RecvackPacket;
use Dongasai\WkProtoPhp\Packets\PingPacket;
use Dongasai\WkProtoPhp\Packets\PongPacket;
use Dongasai\WkProtoPhp\Packets\SubPacket;
use Dongasai\WkProtoPhp\Packets\SubackPacket;
use Dongasai\WkProtoPhp\Packets\EventPacket;

/**
 * 悟空IM协议解析器
 */
class Protocol
{
    public const LATEST_VERSION = 5;
    public const MAX_REMAINING_LENGTH = 1024 * 1024; // 1MB
    public const PAYLOAD_MAX_SIZE = 32767; // MaxInt16

    private array $packetDecodeMap = [];

    public function __construct()
    {
        $this->initPacketDecodeMap();
    }

    /**
     * 初始化包解码映射
     */
    private function initPacketDecodeMap(): void
    {
        $this->packetDecodeMap = [
            FrameType::CONNECT->value => [$this, 'decodeConnect'],
            FrameType::CONNACK->value => [$this, 'decodeConnack'],
            FrameType::PING->value => [$this, 'decodePing'],
            FrameType::PONG->value => [$this, 'decodePong'],
            FrameType::SEND->value => [$this, 'decodeSend'],
            FrameType::SENDACK->value => [$this, 'decodeSendack'],
            FrameType::RECV->value => [$this, 'decodeRecv'],
            FrameType::RECVACK->value => [$this, 'decodeRecvack'],
            FrameType::DISCONNECT->value => [$this, 'decodeDisconnect'],
            FrameType::SUB->value => [$this, 'decodeSub'],
            FrameType::SUBACK->value => [$this, 'decodeSuback'],
            FrameType::EVENT->value => [$this, 'decodeEvent'],
        ];
    }

    /**
     * 解码帧
     */
    public function decodeFrame(string $data, int $version = self::LATEST_VERSION): array
    {
        try {
            // 检查数据长度
            if (strlen($data) < 1) {
                throw new \RuntimeException('Data too short');
            }

            // 解析第一个字节
            $typeAndFlags = ord($data[0]);
            $frameType = ($typeAndFlags >> 4) & 0x0F;

            // 处理PING和PONG（1 字节格式兼容）
            if ($frameType === FrameType::PING->value) {
                if (strlen($data) >= 1) {
                    $framer = Framer::fromUint8($typeAndFlags);
                    $framer->setRemainingLength(0);
                    return [new PingPacket($framer), 1, null];
                }
            }
            
            if ($frameType === FrameType::PONG->value) {
                if (strlen($data) >= 1) {
                    $framer = Framer::fromUint8($typeAndFlags);
                    $framer->setRemainingLength(0);
                    return [new PongPacket($framer), 1, null];
                }
            }

            // 处理其他包类型
            [$framer, $remainingLengthLength] = $this->decodeFramer($data);
            
            if ($framer->getFrameType() === FrameType::UNKNOWN) {
                return [null, 0, null];
            }

            if ($framer->getRemainingLength() > self::MAX_REMAINING_LENGTH) {
                return [null, 0, new \RuntimeException(sprintf('消息超出最大限制[%d]！', self::MAX_REMAINING_LENGTH))];
            }

            $msgLen = 1 + $remainingLengthLength + $framer->getRemainingLength();
            if (strlen($data) < $msgLen) {
                return [null, 0, null]; // 数据不完整，等待更多数据
            }

            $body = substr($data, 1 + $remainingLengthLength, $framer->getRemainingLength());
            
            $decodeFunc = $this->packetDecodeMap[$framer->getFrameType()] ?? null;
            if ($decodeFunc === null) {
                try {
                $frameTypeName = FrameType::from($framer->getFrameType())->toString();
            } catch (\ValueError $e) {
                $frameTypeName = "未知类型[{$framer->getFrameType()}]";
            }
            return [null, 0, new \RuntimeException(sprintf('不支持对[%s]包的解码！', $frameTypeName))];
            }

            $frame = $decodeFunc($framer, $body, $version);
            if ($frame instanceof \Exception) {
                return [null, 0, $frame];
            }

            return [$frame, $msgLen, null];
        } catch (\Exception $e) {
            return [null, 0, $e];
        }
    }

    /**
     * 编码帧
     */
    public function encodeFrame(FrameInterface $frame, int $version = self::LATEST_VERSION): string
    {
        $encoder = new Encoder();
        
        $frameType = $frame->getFrameType();
        
        if ($frameType === FrameType::PING->value || $frameType === FrameType::PONG->value) {
            $encoder->writeUint8(Framer::toFixHeaderUint8($frame));
            return $encoder->getBuffer();
        }

        switch ($frameType) {
            case FrameType::CONNECT->value:
                $packet = $frame;
                $remainingLength = $this->calculateConnectSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeConnect($packet, $encoder, $version);
                break;
                
            case FrameType::CONNACK->value:
                $packet = $frame;
                $remainingLength = $this->calculateConnackSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeConnack($packet, $encoder, $version);
                break;
                
            case FrameType::SEND->value:
                $packet = $frame;
                if (strlen($packet->getPayload()) > self::PAYLOAD_MAX_SIZE) {
                    throw new \RuntimeException(sprintf('消息负载超出最大限制[%d]！', self::PAYLOAD_MAX_SIZE));
                }
                $remainingLength = $this->calculateSendSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeSend($packet, $encoder, $version);
                break;
                
            case FrameType::SENDACK->value:
                $packet = $frame;
                $remainingLength = $this->calculateSendackSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeSendack($packet, $encoder, $version);
                break;
                
            case FrameType::RECV->value:
                $packet = $frame;
                $remainingLength = $this->calculateRecvSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeRecv($packet, $encoder, $version);
                break;
                
            case FrameType::RECVACK->value:
                $packet = $frame;
                $remainingLength = $this->calculateRecvackSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeRecvack($packet, $encoder, $version);
                break;
                
            case FrameType::DISCONNECT->value:
                $packet = $frame;
                $remainingLength = $this->calculateDisconnectSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeDisconnect($packet, $encoder, $version);
                break;
                
            case FrameType::SUB->value:
                $packet = $frame;
                $remainingLength = $this->calculateSubSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeSub($packet, $encoder, $version);
                break;
                
            case FrameType::SUBACK->value:
                $packet = $frame;
                $remainingLength = $this->calculateSubackSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeSuback($packet, $encoder, $version);
                break;
                
            case FrameType::EVENT->value:
                $packet = $frame;
                $remainingLength = $this->calculateEventSize($packet, $version);
                $this->encodeFrameHeader($frame, $encoder, $remainingLength);
                $this->encodeEvent($packet, $encoder, $version);
                break;
                
            // 其他帧类型的编码...
            default:
                try {
                    $frameTypeName = FrameType::from($frameType)->toString();
                } catch (\ValueError $e) {
                    $frameTypeName = "未知类型[{$frameType}]";
                }
                throw new \RuntimeException(sprintf('不支持对[%s]包的编码！', $frameTypeName));
        }

        return $encoder->getBuffer();
    }

    /**
     * 解码帧头
     */
    private function decodeFramer(string $data): array
    {
        if (strlen($data) < 1) {
            throw new \RuntimeException('Data too short for framer');
        }

        $typeAndFlags = ord($data[0]);
        $framer = Framer::fromUint8($typeAndFlags);
        $remainingLengthLength = 0;

        if (strlen($data) < 2) {
            throw new \RuntimeException('Data too short for remaining length');
        }
        [$remainingLength, $remainingLengthLength] = $this->decodeLength(substr($data, 1));
        $framer->setRemainingLength($remainingLength);

        $framer->setFrameSize(strlen($data));
        return [$framer, $remainingLengthLength];
    }

    /**
     * 解码可变长度
     */
    private function decodeLength(string $data): array
    {
        $rLength = 0;
        $multiplier = 1;
        $offset = 0;
        
        while ($offset < 4) { // 最多4字节，防止无限循环
            if ($offset >= strlen($data)) {
                throw new \RuntimeException('Decode length error: incomplete data');
            }
            
            $digit = ord($data[$offset]);
            $rLength += ($digit & 0x7F) * $multiplier;
            
            if (($digit & 0x80) === 0) {
                break;
            }
            
            $multiplier *= 128;
            $offset++;
        }
        
        return [$rLength, $offset + 1];
    }

    /**
     * 编码帧头
     */
    private function encodeFrameHeader(FrameInterface $frame, Encoder $encoder, int $remainingLength): void
    {
        $encoder->writeUint8(Framer::toFixHeaderUint8($frame));
        $encoder->writeVariable($remainingLength);
    }

    // 以下为占位符方法，实际的编码解码逻辑需要在各自的包类中实现
    private function decodeConnect(Framer $framer, string $data, int $version): FrameInterface
    {
        return ConnectPacket::decode($framer, $data, $version);
    }

    private function decodeConnack(Framer $framer, string $data, int $version): FrameInterface
    {
        return ConnackPacket::decode($framer, $data, $version);
    }

    private function decodeSend(Framer $framer, string $data, int $version): FrameInterface
    {
        return SendPacket::decode($framer, $data, $version);
    }

    private function decodeSendack(Framer $framer, string $data, int $version): FrameInterface
    {
        return SendackPacket::decode($framer, $data, $version);
    }

    private function decodeRecv(Framer $framer, string $data, int $version): FrameInterface
    {
        return RecvPacket::decode($framer, $data, $version);
    }

    private function decodeRecvack(Framer $framer, string $data, int $version): FrameInterface
    {
        return RecvackPacket::decode($framer, $data, $version);
    }

    private function decodeDisconnect(Framer $framer, string $data, int $version): FrameInterface
    {
        return DisconnectPacket::decode($framer, $data, $version);
    }

    private function decodeSub(Framer $framer, string $data, int $version): FrameInterface
    {
        return SubPacket::decodePacket($framer, $data);
    }

    private function decodeSuback(Framer $framer, string $data, int $version): FrameInterface
    {
        return SubackPacket::decodePacket($framer, $data);
    }

    private function decodeEvent(Framer $framer, string $data, int $version): FrameInterface
    {
        return EventPacket::decodePacket($framer, $data);
    }

    private function calculateConnectSize($packet, int $version): int
    {
        if ($packet instanceof ConnectPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateConnackSize($packet, int $version): int
    {
        if ($packet instanceof ConnackPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateSendSize($packet, int $version): int
    {
        if ($packet instanceof SendPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateSendackSize($packet, int $version): int
    {
        if ($packet instanceof SendackPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateRecvSize($packet, int $version): int
    {
        if ($packet instanceof RecvPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateRecvackSize($packet, int $version): int
    {
        if ($packet instanceof RecvackPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateDisconnectSize($packet, int $version): int
    {
        if ($packet instanceof DisconnectPacket) {
            return $packet->size($version);
        }
        return 0;
    }

    private function calculateSubSize($packet, int $version): int
    {
        if ($packet instanceof SubPacket) {
            return $packet->encodeSize();
        }
        return 0;
    }

    private function calculateSubackSize($packet, int $version): int
    {
        if ($packet instanceof SubackPacket) {
            return $packet->encodeSize();
        }
        return 0;
    }

    private function calculateEventSize($packet, int $version): int
    {
        if ($packet instanceof EventPacket) {
            return $packet->encodeSize();
        }
        return 0;
    }

    private function encodeConnect($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof ConnectPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeConnack($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof ConnackPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeSend($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof SendPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeSendack($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof SendackPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeRecv($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof RecvPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeRecvack($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof RecvackPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeDisconnect($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof DisconnectPacket) {
            $packet->encode($encoder, $version);
        }
    }

    private function encodeSub($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof SubPacket) {
            $encoder->writeBytes($packet->encode());
        }
    }

    private function encodeSuback($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof SubackPacket) {
            $encoder->writeBytes($packet->encode());
        }
    }

    private function encodeEvent($packet, Encoder $encoder, int $version): void
    {
        if ($packet instanceof EventPacket) {
            $encoder->writeBytes($packet->encode());
        }
    }

    private function decodePing(Framer $framer, string $data, int $version): PingPacket
    {
        $packet = new PingPacket();
        $packet->setFrameType($framer->getFrameType());
        $packet->setRemainingLength($framer->getRemainingLength());
        $packet->setNoPersist($framer->getNoPersist());
        $packet->setRedDot($framer->getRedDot());
        $packet->setSyncOnce($framer->getSyncOnce());
        $packet->setDup($framer->getDup());
        return $packet;
    }

    private function decodePong(Framer $framer, string $data, int $version): PongPacket
    {
        $packet = new PongPacket();
        $packet->setFrameType($framer->getFrameType());
        $packet->setRemainingLength($framer->getRemainingLength());
        $packet->setNoPersist($framer->getNoPersist());
        $packet->setRedDot($framer->getRedDot());
        $packet->setSyncOnce($framer->getSyncOnce());
        $packet->setDup($framer->getDup());
        return $packet;
    }
}